package mushystuff.dtb.core.service.pojo;

import java.util.ArrayList;
import java.util.List;

import mushystuff.dtb.core.dao.ProductDAO;
import mushystuff.dtb.core.dao.UserStoryDAO;
import mushystuff.dtb.core.model.Product;
import mushystuff.dtb.core.model.Sprint;
import mushystuff.dtb.core.model.UserStory;
import mushystuff.dtb.core.service.ProductService;
import mushystuff.dtb.core.service.ServiceException;
import mushystuff.dtb.core.service.to.AssemblerService;
import mushystuff.dtb.core.service.to.AssemblerServiceException;
import mushystuff.dtb.core.service.to.ProductInfoTO;
import mushystuff.dtb.core.service.to.ProductTO;
import mushystuff.dtb.core.service.to.SprintTO;
import mushystuff.dtb.core.service.to.UserStoryTO;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.UnhandledException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
final class ProductServiceImpl implements ProductService {

	private Log log = LogFactory.getLog(this.getClass());

	private ProductDAO productDAO;

	private UserStoryDAO userStoryDAO;

	private AssemblerService assemblerService;

	@Required
	public void setProductDAO(ProductDAO productDAO) {
		this.productDAO = productDAO;
	}

	@Required
	public void setUserStoryDAO(UserStoryDAO userStoryDAO) {
		this.userStoryDAO = userStoryDAO;
	}

	@Required
	public void setAssemblerService(AssemblerService assemblerService) {
		this.assemblerService = assemblerService;
	}

	@Override
	public void createUpdateProduct(ProductTO productTO) {
		if (StringUtils.isEmpty(productTO.getName())) {
			throw new IllegalArgumentException("Name must be set");
		}
		try {
			productDAO.persistProduct(assemblerService.fromTO(Product.class,
					productTO));
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}
	}

	@Override
	public ProductTO getProduct(long productId) {
		Product product = productDAO.getProduct(productId);
		try {
			return assemblerService.toTO(ProductTO.class, product);
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}
	}

	@Override
	@Transactional(readOnly = true)
	public ProductInfoTO[] getAllProductsInfo() {
		List<Product> products = productDAO.getAllProducts();
		try {
			return assemblerService.toTOArray(ProductInfoTO.class, products);
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}
	}

	@Override
	@Transactional(readOnly = true)
	public ProductTO[] getAllActiveProducts() {
		// Adding extra item for select in UI
		// TODO find another way
		ArrayList<Product> res = new ArrayList<Product>();
		Product emptyProduct = new Product();
		emptyProduct.setId(-1L);
		emptyProduct.setName("Please Select");
		res.add(emptyProduct);
		res.addAll(productDAO.getAllActiveProducts());
		try {
			return assemblerService.toTOArray(ProductTO.class, res);
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}
	}

	@Override
	public void addUserStoryToBacklog(long productId,
			UserStoryTO userStoryTO) {
		if (StringUtils.isEmpty(userStoryTO.getStory())) {
			throw new IllegalArgumentException(
					"Story must not be empty for new user story.");
		}
		// Get domain objects
		Product product = productDAO.getProduct(productId);
		UserStory userStory;
		try {
			userStory = assemblerService.fromTO(UserStory.class, userStoryTO);
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}

		boolean added = product.getBacklog().add(userStory);
		if (added) {
			userStory.setProduct(product);
			productDAO.persistProduct(product);
		} else {
			log.warn("User Story " + userStory + " not added to product "
					+ product);
			throw new IllegalArgumentException(
					"User Story must have unique story for product");
		}
	}

	@Override
	public void addSprint(long productId, SprintTO sprintTO) {
		if (StringUtils.isEmpty(sprintTO.getGoal())) {
			throw new IllegalArgumentException(
					"Goal must not be empty for new sprint.");
		}
		// Get domain objects
		Product product = productDAO.getProduct(productId);
		Sprint sprint;
		try {
			sprint = assemblerService.fromTO(Sprint.class, sprintTO);
		} catch (AssemblerServiceException e) {
			throw new ServiceException(e);
		}

		boolean added = product.getSprints().add(sprint);
		if (added) {
			sprint.setProduct(product);
			productDAO.persistProduct(product);
		} else {
			log.warn("Sprint " + sprint + " not added to product " + product);
			throw new IllegalArgumentException(
					"Sprint must have unique goal for product");
		}
	}

	@Override
	public void removeUserStoryFromBacklog(long userStoryId) {
		UserStory userStory = userStoryDAO.getUserStory(userStoryId);
		Product product = userStory.getProduct();
		boolean removed = product.getBacklog().remove(userStory);
		if (removed) {
			productDAO.persistProduct(product);
		} else {
			log.warn("User story " + userStory + " not removed from backlog.");
			throw new RuntimeException("User story not removed from backlog.");
		}
	}

	@Override
	public UserStoryTO getUserStory(long userStoryId) {
		try {
			return assemblerService.toTO(UserStoryTO.class, userStoryDAO
					.getUserStory(userStoryId));
		} catch (AssemblerServiceException e) {
			throw new UnhandledException(e);
		}
	}

}
